// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <ddk/device.h>
#include <ddk/io-buffer.h>
#include <lib/device-protocol/platform-device.h>
#include <ddk/protocol/platform/device.h>
#include <ddk/protocol/usb/dci.h>
#include <ddk/protocol/usb/modeswitch.h>
#include <lib/mmio/mmio.h>
#include <fbl/mutex.h>
#include <lib/zx/handle.h>
#include <lib/zx/interrupt.h>
#include <zircon/device/usb-peripheral.h>
#include <zircon/hw/usb.h>
#include <zircon/listnode.h>
#include <zircon/types.h>

#include <threads.h>

#include <optional>

#include "dwc3-types.h"

// physical endpoint numbers for ep0
#define EP0_OUT 0
#define EP0_IN 1
#define EP0_FIFO EP0_OUT

#define EP_OUT(ep_num) (((ep_num)&1) == 0)
#define EP_IN(ep_num) (((ep_num)&1) == 1)

#define EVENT_BUFFER_SIZE PAGE_SIZE
#define EP0_MAX_PACKET_SIZE 512
#define DWC3_MAX_EPS 32

// converts a USB endpoint address to 0 - 31 index
#define dwc3_ep_num(addr) ((((addr)&0xF) << 1) | !!((addr)&USB_DIR_IN))

typedef enum {
  EP0_STATE_NONE,
  EP0_STATE_SETUP,          // Queued setup phase
  EP0_STATE_DATA_OUT,       // Queued data on EP0_OUT
  EP0_STATE_DATA_IN,        // Queued data on EP0_IN
  EP0_STATE_WAIT_NRDY_OUT,  // Waiting for not-ready on EP0_OUT
  EP0_STATE_WAIT_NRDY_IN,   // Waiting for not-ready on EP0_IN
  EP0_STATE_STATUS,         // Waiting for status to complete
} dwc3_ep0_state;

typedef struct {
  io_buffer_t buffer;
  dwc3_trb_t* first;    // first TRB in the fifo
  dwc3_trb_t* next;     // next free TRB in the fifo
  dwc3_trb_t* current;  // TRB for currently pending transaction
  dwc3_trb_t* last;     // last TRB in the fifo (link TRB)
} dwc3_fifo_t;

typedef struct {
  dwc3_fifo_t fifo;
  list_node_t queued_reqs;     // requests waiting to be processed
  usb_request_t* current_req;  // request currently being processed
  unsigned rsrc_id;            // resource ID for current_req

  // Used for synchronizing endpoint state
  // and ep specific hardware registers
  // This should be acquired before dwc3_t.lock
  // if acquiring both locks.
  fbl::Mutex lock;

  uint16_t max_packet_size;
  uint8_t ep_num;
  bool enabled;
  uint8_t type;  // control, bulk, interrupt or isochronous
  uint8_t interval;
  // TODO(voydanoff) USB 3 specific stuff here

  bool got_not_ready;
  bool stalled;
} dwc3_endpoint_t;

typedef struct {
  zx_device_t* zxdev = nullptr;
  zx_device_t* pdev_dev = nullptr;
  zx_device_t* xhci_dev = nullptr;
  zx_device_t* parent = nullptr;
  pdev_protocol_t pdev;
  usb_mode_switch_protocol_t ums;
  usb_dci_interface_protocol_t dci_intf;
  std::optional<ddk::MmioBuffer> mmio;
  zx::handle bti_handle;

  usb_mode_t usb_mode = USB_MODE_NONE;
  bool start_device_on_xhci_release;
  fbl::Mutex usb_mode_lock;  // protects usb_mode and start_device_on_xhci_release

  // event stuff
  io_buffer_t event_buffer;
  zx::interrupt irq_handle;
  thrd_t irq_thread;

  dwc3_endpoint_t eps[DWC3_MAX_EPS];

  // connection state
  usb_speed_t speed = USB_SPEED_UNDEFINED;

  // ep0 stuff
  usb_setup_t cur_setup;  // current setup request
  io_buffer_t ep0_buffer;
  dwc3_ep0_state ep0_state;

  // Used for synchronizing global state
  // and non ep specific hardware registers.
  // dwc3_endpoint_t.lock should be acquired first
  // if acquiring both locks.
  fbl::Mutex lock;
  bool configured = false;
  fbl::Mutex pending_completions_lock;
  list_node_t pending_completions __TA_GUARDED(pending_completions_lock);
} dwc3_t;

// Internal context for USB requests
typedef struct {
  // callback to the upper layer
  usb_request_complete_t complete_cb;
  // for queueing requests internally
  list_node_t node;
  list_node_t pending_node;
} dwc_usb_req_internal_t;

#define USB_REQ_TO_INTERNAL(req) \
  ((dwc_usb_req_internal_t*)((uintptr_t)(req) + sizeof(usb_request_t)))
#define INTERNAL_TO_USB_REQ(ctx) ((usb_request_t*)((uintptr_t)(ctx) - sizeof(usb_request_t)))

static inline ddk::MmioBuffer* dwc3_mmio(dwc3_t* dwc) { return &*dwc->mmio; }

void dwc3_usb_reset(dwc3_t* dwc);
void dwc3_disconnected(dwc3_t* dwc);
void dwc3_connection_done(dwc3_t* dwc);
void dwc3_set_address(dwc3_t* dwc, unsigned address);
void dwc3_reset_configuration(dwc3_t* dwc);

// Commands
void dwc3_cmd_start_new_config(dwc3_t* dwc, unsigned ep_num, unsigned resource_index);
void dwc3_cmd_ep_set_config(dwc3_t* dwc, unsigned ep_num, unsigned ep_type,
                            unsigned max_packet_size, unsigned interval, bool modify);
void dwc3_cmd_ep_transfer_config(dwc3_t* dwc, unsigned ep_num);
void dwc3_cmd_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, zx_paddr_t trb_phys);
void dwc3_cmd_ep_end_transfer(dwc3_t* dwc, unsigned ep_num);
void dwc3_cmd_ep_set_stall(dwc3_t* dwc, unsigned ep_num);
void dwc3_cmd_ep_clear_stall(dwc3_t* dwc, unsigned ep_num);

// Endpoints
zx_status_t dwc3_ep_fifo_init(dwc3_t* dwc, unsigned ep_num);
void dwc3_ep_fifo_release(dwc3_t* dwc, unsigned ep_num);
zx_status_t dwc3_ep_config(dwc3_t* dwc, const usb_endpoint_descriptor_t* ep_desc,
                           const usb_ss_ep_comp_descriptor_t* ss_comp_desc);
void dwc3_ep_set_config(dwc3_t* dwc, unsigned ep_num, bool enable);
zx_status_t dwc3_ep_disable(dwc3_t* dwc, uint8_t ep_addr);
void dwc3_start_eps(dwc3_t* dwc);
void dwc3_ep_queue(dwc3_t* dwc, unsigned ep_num, usb_request_t* req);
void dwc3_ep_start_transfer(dwc3_t* dwc, unsigned ep_num, unsigned type, zx_paddr_t buffer,
                            size_t length, bool send_zlp);
void dwc3_ep_xfer_started(dwc3_t* dwc, unsigned ep_num, unsigned rsrc_id);
void dwc3_ep_xfer_complete(dwc3_t* dwc, unsigned ep_num);
void dwc3_ep_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage);
zx_status_t dwc3_ep_set_stall(dwc3_t* dwc, unsigned ep_num, bool stall);
void dwc3_ep_end_transfers(dwc3_t* dwc, unsigned ep_num, zx_status_t reason);
void dwc_ep_read_trb(dwc3_endpoint_t* ep, dwc3_trb_t* trb, dwc3_trb_t* out_trb);

// Endpoint 0
zx_status_t dwc3_ep0_init(dwc3_t* dwc);
void dwc3_ep0_reset(dwc3_t* dwc);
void dwc3_ep0_start(dwc3_t* dwc);
void dwc3_ep0_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage);
void dwc3_ep0_xfer_complete(dwc3_t* dwc, unsigned ep_num);

// Events
void dwc3_events_start(dwc3_t* dwc);
void dwc3_events_stop(dwc3_t* dwc);

// Utils
void dwc3_print_status(dwc3_t* dwc);

__BEGIN_CDECLS
zx_status_t dwc3_bind(void* ctx, zx_device_t* parent);
__END_CDECLS
